package com.apinji.unionpay.service.serviceimpl;

import com.apinji.common.domain.TransactionRecord;
import com.apinji.common.domain.mapper.TransactionRecordMapper;
import com.apinji.common.domain.wrapper.Reply;
import com.apinji.common.plugin.DateUtils;
import com.apinji.common.plugin.Mutual;
import com.apinji.common.plugin.StrUtils;
import com.apinji.common.plugin.UnionpayUtils;
import com.apinji.common.plugin.fastjson.JSONUtils;
import com.apinji.common.data.PayStatusEnum;
import com.apinji.common.data.ConsumeTypeEnum;
import com.apinji.common.data.PayChannelEnum;
import com.apinji.unionpay.common.UnionpayErrorCodeEnum;
import com.apinji.unionpay.dao.PayDao;
import com.apinji.unionpay.dao.TransactionRecordDao;
import com.apinji.unionpay.dto.RefundDto;
import com.apinji.unionpay.service.RefundService;
import com.apinji.unionpay.sdk.AcpService;
import com.apinji.unionpay.sdk.LogUtil;
import com.apinji.unionpay.sdk.SDKConfig;
import com.apinji.unionpay.sdk.SDKConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * Created by 轩辕夏夏 on 2017/7/11.
 */
@Service
public class RefundServiceImpl implements RefundService {

    @Autowired
    private TransactionRecordMapper transactionRecordMapper;

    @Autowired
    private TransactionRecordDao transactionRecordDao;

    @Autowired
    private PayDao payDao;

    @Override
    public Reply refundOpt(HttpServletRequest request, RefundDto refundDto) {
        String userUUID =  refundDto.getUserUUID();
        String consumeOutOrderId = refundDto.getConsumeOutOrderId();
        String refundOutOderId = refundDto.getRefundOutOderId();
        int txnAmt = refundDto.getTxnAmt();
        String frontUrl = refundDto.getFrontUrl();

        //1.先获取到交易流水，主要是  queryId
        List<TransactionRecord> records = transactionRecordMapper.listRecords(consumeOutOrderId, userUUID, PayStatusEnum.SUCCESS_DEAL.getStatus(), ConsumeTypeEnum.PAYMENT_TYPE.getCode());
        if(null == records || records.size() == 0) {
            return Mutual.notExist("没有查到需要退款的交易记录");
        }
        if(records.size() > 1) {
            return Mutual.haveExist("存在多条需要退款的交易记录");
        }
        TransactionRecord transactionRecord = records.get(0);
//        if(transactionRecord.getConsumeType() != ConsumeTypeEnum.PAYMENT_TYPE.getCode()) {
//            return Mutual.serverError("该笔订单不是支付订单，不能进行退款操作");
//        }
        if(transactionRecord.getPayChannel() != PayChannelEnum.UNIONPAY_CHANNEL.getCode()) {
            return Mutual.serverError("该笔订单不是银联支付订单，不是进行银联退款操作");
        }
        if(StrUtils.isEmpty(transactionRecord.getQueryId())) {
            return Mutual.serverError("没有查询到订单记录");
        }
        //退款金额校验
        if(transactionRecord.getTxnAmt() < txnAmt) {
            return Mutual.invalidParam("退款金额超出已支付金额");
        }
        String refundRecordId = StrUtils.uuid();
        //
        Map<String, String> data = new HashMap<String, String>();
        /***银联全渠道系统，产品参数，除了encoding自行选择外其他不需修改***/
        data.put("version", SDKConfig.getConfig().getVersion());               //版本号
        data.put("encoding", "UTF-8");             //字符集编码 可以使用UTF-8,GBK两种方式
        data.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法
        String txnType = "04";
        data.put("txnType", txnType);                           //交易类型 04-退货
        String txbSubType = "00";
        data.put("txnSubType", txbSubType);                        //交易子类型  默认00
        String bizType = "000902";
        data.put("bizType", bizType);                       //业务类型
        String channelType = "07";
        data.put("channelType", channelType);                       //渠道类型，07-PC，08-手机

        /***商户接入参数***/
        data.put("merId", SDKConfig.getConfig().getMerId());                //商户号码，请改成自己申请的商户号或者open上注册得来的777商户号测试
        data.put("accessType", "0");                         //接入类型，商户接入固定填0，不需修改
        data.put("orderId", refundOutOderId);          //商户订单号，8-40位数字字母，不能含“-”或“_”，可以自行定制规则，重新产生，不同于原消费
        String txnTime = DateUtils.getCurrentDateTime();
        data.put("txnTime", txnTime);      //订单发送时间，格式为YYYYMMDDhhmmss，必须取当前时间，否则会报txnTime无效
        String currencyCode = "156";
        data.put("currencyCode", currencyCode);                     //交易币种（境内商户一般是156 人民币）
        data.put("txnAmt", txnAmt+"");                          //****退货金额，单位分，不要带小数点。退货金额小于等于原消费金额，当小于的时候可以多次退货至退货累计金额等于原消费金额
        String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
//        String backUrl = basePath + "/v1.0/refund/backRcvResponse";
        LogUtil.writeMessage("refund backUrl-------->" + SDKConfig.getConfig().getBackRequestUrl());
        data.put("backUrl", SDKConfig.getConfig().getBackRequestUrl());               //后台通知地址，后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 退货交易 商户通知,其他说明同消费交易的后台通知

        /***要调通交易以下字段必须修改***/
        data.put("origQryId", transactionRecord.getQueryId());      //****原消费交易返回的的queryId，可以从消费交易后台通知接口中或者交易状态查询接口中获取

        /**请求参数设置完毕，以下对请求参数进行签名并发送http post请求，接收同步应答报文------------->**/

        // 请求方保留域，
        // 透传字段，查询、通知、对账文件中均会原样出现，如有需要请启用并修改自己希望透传的数据。
        // 出现部分特殊字符时可能影响解析，请按下面建议的方式填写：
        // 1. 如果能确定内容不会出现&={}[]"'等符号时，可以直接填写数据，建议的方法如下。
//		data.put("reqReserved", "透传信息1|透传信息2|透传信息3");
        data.put("reqReserved", refundRecordId);
        // 2. 内容可能出现&={}[]"'符号时：
        // 1) 如果需要对账文件里能显示，可将字符替换成全角＆＝｛｝【】“‘字符（自己写代码，此处不演示）；
        // 2) 如果对账文件没有显示要求，可做一下base64（如下）。
        //    注意控制数据长度，实际传输的数据长度不能超过1024位。
        //    查询、通知等接口解析时使用new String(Base64.decodeBase64(reqReserved), "UTF-8");解base64后再对数据做后续解析。
//		data.put("reqReserved", Base64.encodeBase64String("任意格式的信息都可以".toString().getBytes("UTF-8")));



        //======================
        TransactionRecord refundRecord = new TransactionRecord();

        refundRecord.setId(refundRecordId);
        refundRecord.setUserUUID(userUUID);
        refundRecord.setCreateTime(new Date());
        refundRecord.setPayStatus(PayStatusEnum.ORDER_CREATE.getStatus());
        refundRecord.setCardNo(transactionRecord.getCardNo());
        refundRecord.setOrderId(transactionRecord.getOrderId());
        refundRecord.setFrontUrl(frontUrl);
        refundRecord.setConsumeType(ConsumeTypeEnum.REFUND_TYPE.getCode());//退款
        refundRecord.setConsumeTypeName(ConsumeTypeEnum.REFUND_TYPE.getName());

        refundRecord.setOutOrderId(refundOutOderId);//对外的订单编号 UUID
        refundRecord.setPayChannel(PayChannelEnum.UNIONPAY_CHANNEL.getCode());

        refundRecord.setTxnType(txnType);
        refundRecord.setTxnSubType(txbSubType);
        refundRecord.setBizType(bizType);
        refundRecord.setChannelType(channelType);//渠道类型
        refundRecord.setMerId(SDKConfig.getConfig().getMerId());
        refundRecord.setTxnTime(txnTime);//订单发送时间
        refundRecord.setTxnAmt(txnAmt);//订单金额，单位 分，不含小数点
        refundRecord.setCurrencyCode(currencyCode);//交易币种
        refundRecord.setToken(transactionRecord.getToken());
        //
        refundRecord.setMemo(PayStatusEnum.ORDER_CREATE.getName());//1
        //
        transactionRecordMapper.addTransactionRecord(refundRecord);
        //======================

        Map<String, String> reqData  = AcpService.sign(data,"UTF-8");			//报文中certId,signature的值是在signData方法中获取并自动赋值的，只要证书配置正确即可。
        String url = SDKConfig.getConfig().getBackRequestUrl();									 	//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        Map<String, String> rspData = AcpService.post(reqData, url,"UTF-8");	//这里调用signData之后，调用submitUrl之前不能对submitFromData中的键值对做任何修改，如果修改会导致验签不通过

        /**对应答码的处理，请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/

        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if(!rspData.isEmpty()){
            if(AcpService.validate(rspData, "UTF-8")){
                LogUtil.writeLog("验证签名成功");
                String respCode = rspData.get("respCode") ;
                if(("00").equals(respCode)){
                    //交易已受理(不代表交易已成功），等待接收后台通知更新订单状,也可以主动发态起 查询交易确定交易状态。
                    //TODO
                    transactionRecordDao.updatePayStatus(refundRecordId, PayStatusEnum.BANK_DEAL.getStatus(), PayStatusEnum.BANK_DEAL.getName());
                    return Mutual.success(transactionRecord);
                }else if(("03").equals(respCode)||
                        ("04").equals(respCode)||
                        ("05").equals(respCode)){
                    //后续需发起交易状态查询交易确定交易状态
                    //TODO
                    return Mutual.serverError("后续需发起交易状态查询交易确定交易状态");
                }else{
                    //其他应答码为失败请排查原因
                    //TODO
                    transactionRecordDao.updatePayStatus(refundRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "退款失败，银联返回错误码为(" + respCode+")，" + UnionpayErrorCodeEnum.getMsg(respCode));
                    return Mutual.unionpayError(respCode);
                }
            }else{
                LogUtil.writeErrorLog("验证签名失败");
                //TODO 检查验证签名失败的原因
                transactionRecordDao.updatePayStatus(refundRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "验证签名失败");
                return Mutual.serverError("验证签名失败");
            }
        }else{
            //未返回正确的http状态
            LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
            transactionRecordDao.updatePayStatus(refundRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), "未获取到返回报文或返回http状态码非200");
            return Mutual.serverError("未获取到返回报文或返回http状态码非200");
        }
        //String reqMessage = DemoBase.genHtmlResult(reqData);
        //String rspMessage = DemoBase.genHtmlResult(rspData);
        //resp.getWriter().write("请求报文:<br/>"+reqMessage+"<br/>" + "应答报文:</br>"+rspMessage+"");
    }



    @Override
    public void backRcvResponse(HttpServletRequest req, HttpServletResponse res) throws Exception {
        LogUtil.writeLog("BackRcvResponse接收后台通知开始");

        String encoding = req.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = UnionpayUtils.getAllRequestParam(req);

        LogUtil.printRequestLog(reqParam);

        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<String, String>(reqParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = (String) e.getKey();
                String value = (String) e.getValue();

                valideData.put(key, value);
            }
        }

        String transactionRecordId = valideData.get("reqReserved");
        //
        if(StrUtils.isEmpty(transactionRecordId)) {
            LogUtil.writeLog("银联回调transactionRecordId为空");
            return;
        }

        TransactionRecord transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
        if(null != transactionRecord && transactionRecord.getPayStatus() == PayStatusEnum.SUCCESS_DEAL.getStatus()) {
            LogUtil.writeLog("银联已经成功回调，且状态已经为成功");
            //返回给银联服务器http 200状态码
            res.getWriter().print("ok");
            return;
        }

        //
        if(null == transactionRecord) {
            LogUtil.writeLog("ID为："+transactionRecordId+"的交易记录不存在");
            return;
        }

        transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_ONLY.getStatus(), PayStatusEnum.CALLBACK_ONLY.getName());


        //重要！验证签名前不要修改reqParam中的键值对的内容，否则会验签不过
        if (!AcpService.validate(valideData, encoding)) {
            LogUtil.writeLog("验证签名结果[失败].");
            //验签失败，需解决验签问题
            transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_VALIDATE_FAIL.getStatus(), PayStatusEnum.CALLBACK_VALIDATE_FAIL.getName());
        } else {
            LogUtil.writeLog("验证签名结果[成功].");
            //
            transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.CALLBACK_VALIDATE_SUCCESS.getStatus(), PayStatusEnum.CALLBACK_VALIDATE_SUCCESS.getName());

            //补充银联回调信息
            String queryId = valideData.get("queryId"); //查询流水号   由银联返回，用于在后续类交易中唯一标识一笔交易
            String traceNo = valideData.get("traceNo"); //系统跟踪号   收单机构对账时使用，该域由银联系统产生
            String traceTime = valideData.get("traceTime");//交易传输时间   （月月日日时时分分秒秒）24小时制收单机构对账时使用，该域由银联系统产生
            String settleDate = valideData.get("settleDate");//清算日期   为银联和入网机构间的交易结算日期。一般前一日23点至当天23点为一个清算日。也就是23点前的交易，当天23点之后开始结算，23点之后的交易，要第二天23点之后才会结算。测试环境为测试需要，23:30左右日切，所以23:30到23:30为一个清算日，当天23:30之后为下个清算日。
            String settleCurrencyCode = valideData.get("settleCurrencyCode");//清算币种   境内返回156
            String settleAmt = valideData.get("settleAmt");//清算金额    取值同交易金额
            String respMsg = valideData.get("respMsg");//应答信息
            transactionRecordMapper.updateCallBackInfo(transactionRecordId, queryId, traceNo, traceTime, settleDate, settleCurrencyCode, settleAmt, respMsg);

//            String customerInfo = valideData.get("customerInfo");
//            if(null!=customerInfo){
//                Map<String,String>  customerInfoMap = AcpService.parseCustomerInfo(customerInfo, "UTF-8");
//                LogUtil.writeLog("customerInfoMap明文: "+ customerInfoMap);
//            }

            //String accNo = valideData.get("accNo");
            //如果配置了敏感信息加密证书，可以用以下方法解密，如果不加密可以不需要解密
//            if(null!=accNo){
//                accNo = AcpService.decryptData(accNo, "UTF-8");
//                LogUtil.writeLog("accNo明文: "+ accNo);
//            }

//            String tokenPayData = valideData.get("tokenPayData");
//            if(null!=tokenPayData){
//                Map<String,String> tokenPayDataMap = SDKUtil.parseQString(tokenPayData.substring(1, tokenPayData.length() - 1));
//                String token = tokenPayDataMap.get("token");//这样取
//                LogUtil.writeLog("tokenPayDataMap明文: " + tokenPayDataMap);
//            }

            String respCode = valideData.get("respCode");
            //判断respCode=00、A6后，对涉及资金类的交易，请再发起查询接口查询，确定交易成功后更新数据库。
            if("00".equals(respCode)) {
                //再次发起查询，确定交易成功
                Reply reply = payDao.consumeQuery(transactionRecord.getId(), transactionRecord.getUserUUID());
                if(!reply.getSuccess()) {
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.FAILD_DEAL.getStatus(), PayStatusEnum.FAILD_DEAL.getName()+"：再次发起查询，返回失败的结果");
                    return;
                }
                //
                transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
                String userUUID = "";
                String frontUrl = "";
                if(null != transactionRecord) {
                    userUUID = transactionRecord.getUserUUID();
                    frontUrl = transactionRecord.getFrontUrl();
                }
                if(StrUtils.isEmpty(userUUID)) {
                    return;
                }
                //
                LogUtil.writeLog("userUUID: " + userUUID + "      transactionRecordId：" + transactionRecordId + "         frontUrl：" + frontUrl);
                if(null != transactionRecord && transactionRecord.getPayStatus() != PayStatusEnum.SUCCESS_DEAL.getStatus()) {
                    transactionRecordDao.updatePayStatus(transactionRecordId, PayStatusEnum.SUCCESS_DEAL.getStatus(), PayStatusEnum.SUCCESS_DEAL.getName());
                }
                //




                transactionRecord = transactionRecordMapper.getOneById(transactionRecordId);
                Map<String, String> reqData = new HashMap<String, String>();
                //
                String str = JSONUtils.parseJsonStr(transactionRecord);
                reqData.put("transactionRecord", str);
                //回调：通知前台
                Map<String, String> rspData = UnionpayUtils.post(reqData, frontUrl,"UTF-8");
                //
                if(null != rspData) {
                    transactionRecordMapper.updateFrontRspStatus(transactionRecordId, rspData.get("rspStatus"));
                }
                //返回给银联服务器http 200状态码
                res.getWriter().print("ok");
            }
        }
        LogUtil.writeLog("BackRcvResponse接收后台通知结束");
    }

}
